package com.atguigu.gulimall.ware.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.atguigu.common.constant.WareConstant;
import com.atguigu.common.exception.BizCodeEnum;
import com.atguigu.common.exception.NoStockException;
import com.atguigu.common.to.SkuHasStockTo;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;
import com.atguigu.common.utils.R;
import com.atguigu.common.vo.mq.OrderTo;
import com.atguigu.common.vo.mq.StockLockedTo;
import com.atguigu.common.vo.mq.WareOrderTaskDetailTo;
import com.atguigu.gulimall.ware.dao.WareSkuDao;
import com.atguigu.gulimall.ware.entity.WareOrderTaskDetailEntity;
import com.atguigu.gulimall.ware.entity.WareOrderTaskEntity;
import com.atguigu.gulimall.ware.entity.WareSkuEntity;
import com.atguigu.gulimall.ware.feign.OrderFeignService;
import com.atguigu.gulimall.ware.feign.ProductFeignService;
import com.atguigu.gulimall.ware.service.WareOrderTaskDetailService;
import com.atguigu.gulimall.ware.service.WareOrderTaskService;
import com.atguigu.gulimall.ware.service.WareSkuService;
import com.atguigu.gulimall.ware.vo.OrderItemVo;
import com.atguigu.gulimall.ware.vo.OrderVo;
import com.atguigu.gulimall.ware.vo.WareSkuLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.Data;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

    @Resource
    private WareSkuDao wareSkuDao;
    @Resource
    private ProductFeignService productFeignService;
    @Resource
    private RabbitTemplate rabbitTemplate;
    @Resource
    private WareOrderTaskService wareOrderTaskService;
    @Resource
    private WareOrderTaskDetailService wareOrderTaskDetailService;
    @Resource
    private OrderFeignService orderFeignService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {

        LambdaQueryWrapper<WareSkuEntity> wrapper = new LambdaQueryWrapper<>();

        String skuId = (String) params.get("skuId");
        wrapper.eq(StringUtils.hasText(skuId), WareSkuEntity::getSkuId, skuId);

        String wareId = (String) params.get("wareId");
        wrapper.eq(StringUtils.hasText(wareId), WareSkuEntity::getWareId, wareId);

        IPage<WareSkuEntity> page = this.page(new Query<WareSkuEntity>().getPage(params), wrapper);

        return new PageUtils(page);
    }

    @Override
    public void addStock(Long skuId, Long wareId, Integer skuNum) {
        LambdaQueryWrapper<WareSkuEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(WareSkuEntity::getSkuId, skuId).eq(WareSkuEntity::getWareId, wareId);
        WareSkuEntity entity = wareSkuDao.selectOne(wrapper);
        if (ObjectUtil.isNull(entity)) {
            entity = new WareSkuEntity();
            // 远程调用，查询商品名称，如果失败，捕捉异常不抛出，避免回滚整个事务，因为添加冗余字段的操作并不是必需的
            try {
                R r = productFeignService.getSkuNameById(skuId);
                if (r.getCode() == 0) {
                    String skuName = (String) r.get("skuName");
                    entity.setSkuName(skuName);
                }
            } catch (Exception e) {
            }

            entity.setSkuId(skuId);
            entity.setStock(skuNum);
            entity.setWareId(wareId);
        } else {
            entity.setStockLocked(entity.getStockLocked() + skuNum);
        }
        this.saveOrUpdate(entity);
    }

    @Override
    public List<SkuHasStockTo> getSkusHasStock(List<Long> skuIds) {
        // select sku_id, sum(stock - stock_locked) stock from wms_ware_sku group by sku_id
        List<WareSkuEntity> resultList = this.baseMapper.getSkuStockList(skuIds);

        List<SkuHasStockTo> collect0 = resultList.stream().map(item -> {
            SkuHasStockTo skuHasStockTo = new SkuHasStockTo();
            skuHasStockTo.setSkuId(item.getSkuId());
            skuHasStockTo.setHasStock(item.getStock() > 0);
            return skuHasStockTo;
        }).collect(Collectors.toList());

        List<Long> hasRecords = resultList.stream().map(WareSkuEntity::getSkuId).collect(Collectors.toList());
        List<Long> noRecords = skuIds.stream().filter(skuId -> !hasRecords.contains(skuId)).collect(Collectors.toList());
        List<SkuHasStockTo> collect1 = noRecords.stream().map(skuId -> {
            SkuHasStockTo skuHasStockTo = new SkuHasStockTo();
            skuHasStockTo.setSkuId(skuId);
            skuHasStockTo.setHasStock(false);
            return skuHasStockTo;
        }).collect(Collectors.toList());

        // 合并 collect0 和 collect1
        collect0.addAll(collect1);
        return collect0;
    }

    /**
     * 为订单中的商品锁定库存
     * 需要解锁库存的场景
     * 1. 提交订单成功，但是订单超时未支付被关闭或者用户手动关闭订单，需要解锁库存
     * 2. 提交订单成功、库存锁定成功，但是提交订单调用其他服务失败，导致订单回滚，需要解锁库存
     *
     * @param vo
     */
    @Transactional
    @Override
    public void orderLockStock(WareSkuLockVo vo) {
        // 锁定库存前，保存库存锁定单数据，方便后续解锁库存
        WareOrderTaskEntity wareOrderTaskEntity = new WareOrderTaskEntity();
        wareOrderTaskEntity.setOrderSn(vo.getOrderSn());
        wareOrderTaskService.save(wareOrderTaskEntity);

        // 锁定库存时，应当根据订单的收货地址，查询出最靠近的仓库，然后锁定库存。本项目不考虑仓库距离因素
        // 确定订单中所有订单项对应的商品在哪些仓库有库存
        List<OrderItemVo> orderItemVoList = vo.getLocks();
        List<SkuWareHasStock> skuWareHasStockList = orderItemVoList.stream().map(item -> {
            SkuWareHasStock skuWareHasStock = new SkuWareHasStock();
            skuWareHasStock.setSkuId(item.getSkuId());
            List<Long> wareIdList = wareSkuDao.listWareIdHasStockBySkuId(item.getSkuId());
            skuWareHasStock.setWareIdList(wareIdList);
            skuWareHasStock.setNum(item.getCount());
            return skuWareHasStock;
        }).collect(Collectors.toList());

        // 锁定库存
        for (SkuWareHasStock skuWareHasStock : skuWareHasStockList) {
            // 标记每个商品是否锁定库存成功
            boolean skuStockLocked = false;
            // 商品（skuId）在哪些仓库（wareIdList）有库存
            Long skuId = skuWareHasStock.getSkuId();
            List<Long> wareIdList = skuWareHasStock.getWareIdList();

            if (CollectionUtil.isNotEmpty(wareIdList)) {
                // 遍历所有有库存的仓库，当前仓库锁定库存失败，遍历到下一个仓库，继续尝试锁定库存，一旦锁定库存成功，就结束循环
                for (Long wareId : wareIdList) {
                    // UPDATE wms_ware_sku SET stock_locked = stock_locked + #{num} WHERE sku_id = #{skuId} AND ware_id = #{wareId} AND stock - stock_locked >= #{num}
                    Long affectRowNum = wareSkuDao.lockSkuStock(skuId, wareId, skuWareHasStock.getNum());
                    if (affectRowNum == 1) {
                        skuStockLocked = true;
                        // 当前订单项对应的商品锁定库存成功，保存库存锁定详情单数据
                        WareOrderTaskDetailEntity wareOrderTaskDetailEntity = new WareOrderTaskDetailEntity(null, skuId, "", skuWareHasStock.getNum(), wareOrderTaskEntity.getId(), wareId, 1);
                        wareOrderTaskDetailService.save(wareOrderTaskDetailEntity);
                        // 每个单项对应的商品锁定库存成功都发送消息到 MQ，即使后续有商品锁定库存失败，触发整个锁定库存方法回滚，因为保存的库存锁定单和库存锁定详情单数据也会回滚，根据消息中的库存锁定单 id 和库存锁定详情单 id 查询不到数据，自然意味着库存锁定失败执行了回滚操作，也就无所谓执行锁定库存的逆操作来解锁库存
                        // 发送消息给 MQ，消息内容是库存锁定单的 id 和保存的库存锁定详情单
                        // 实际上这里发送消息时，可以只发送库存锁定单 ID 和库存锁定详情单 ID
                        // 根据库存锁定单 id 可以确定为哪个订单锁定库存成功
                        // 根据库存锁定详情单，可以确定为哪个订单下的哪个商品锁定了多少的库存
                        StockLockedTo stockLockedTo = new StockLockedTo();
                        stockLockedTo.setId(wareOrderTaskEntity.getId());
                        WareOrderTaskDetailTo detailTo = new WareOrderTaskDetailTo();
                        BeanUtils.copyProperties(wareOrderTaskDetailEntity, detailTo);
                        stockLockedTo.setDetailTo(detailTo);
                        rabbitTemplate.convertAndSend(WareConstant.STOCK_EVENT_EXCHANGE, WareConstant.STOCK_LOCKED_KEY, stockLockedTo);
                        break;
                    }
                }
                if (!skuStockLocked) {
                    // 当前商品在所有有库存的仓库都没有成功锁定库存
                    throw new NoStockException(skuId);
                }
            } else {
                // 所有仓库都没有该商品，抛出异常
                throw new NoStockException(skuId);
            }
        }
        // 执行到这里，没有抛出异常，就表示订单中的所有商品锁定库存成功
    }

    @Data
    static class SkuWareHasStock {
        /**
         * 商品 skuId
         */
        private Long skuId;
        /**
         * 锁定库存数
         */
        private Integer num;
        /**
         * 库存中有指定商品的仓库 ID 集合
         */
        private List<Long> wareIdList;
    }

    /**
     * 根据消息内容，解锁库存
     * @param stockLockedTo
     */
    @Override
    public void unlockStock(StockLockedTo stockLockedTo) {
        // 库存锁定单 ID
        Long wareOrderTaskId = stockLockedTo.getId();
        // 库存锁定详情单 ID
        Long wareOrderTaskDetailId = stockLockedTo.getDetailTo().getId();
        WareOrderTaskDetailEntity wareOrderTaskDetail = wareOrderTaskDetailService.getById(wareOrderTaskDetailId);
        if (wareOrderTaskDetail != null && wareOrderTaskDetail.getLockStatus() == 1) {
            // 如果 wareOrderTaskDetail 为 null，说明锁定库存方法触发事务回滚，无需执行锁定库存的逆操作来解锁库存
            // 如果 wareOrderTaskDetail 不为 null，但是 wareOrderTaskDetail.getLockStatus() 不为 1，说明之前已经根据库存锁定详情单解锁过库存或已经扣减了库存，无需再次解锁
            // 存在 wareOrderTaskDetail 不为 null，而且 wareOrderTaskDetail.getLockStatus() 为 1（库存锁定状态：1-已锁定 2-已解锁 3-扣减），说明库存锁定成功而且生成了库存锁定详情单，此时需要根据订单状态，决定是否需要执行库存锁定的逆操作来解锁库存
            WareOrderTaskEntity wareOrderTask = wareOrderTaskService.getById(wareOrderTaskId);
            // 锁定库存的订单商品所属的订单号
            String orderSn = wareOrderTask.getOrderSn();
            // 远程调用 order 服务查询订单，获取订单状态
            R r = orderFeignService.getOrderByOrderSn(orderSn);
            if (r.getCode() == 0) {
                // 远程调用失败，没有显式地手动 ACK，RabbitMQ 会自动处理消息的重发逻辑
                // 远程调用成功
                Object data = r.get("data");
                if (data != null) {
                    OrderVo orderVo = JSONUtil.toBean(JSONUtil.toJsonStr(data), OrderVo.class);
                    Integer status = orderVo.getStatus();
                    if (4 == status) {
                        // 订单已关闭，解锁库存
                        unlockStock(wareOrderTaskDetail);
                    }
                } else {
                    // order 服务中提交订单时触发事务回滚，但是 ware 服务中锁定库存成功，需要执行锁定库存的逆操作来解锁库存
                    unlockStock(wareOrderTaskDetail);
                }
            } else {
                // 远程调用失败，需要抛出异常，方便消息监听器中的 handler 方法捕获异常，触发消息重发逻辑
                throw new RuntimeException(BizCodeEnum.CALL_OTHER_SERVICE_EXCEPTION.getMsg());
            }
        }
    }

    /**
     * 收到订单关闭消息，检查是否需要解锁库存，避免订单被关闭后，锁定的库存永远得不到释放
     * @param orderTo
     */
    @Transactional
    @Override
    public void unlockStock(OrderTo orderTo) {
        // 根据订单号查询库存锁定单
        String orderSn = orderTo.getOrderSn();
        WareOrderTaskEntity wareOrderTask = wareOrderTaskService.getOrderTaskByOrderSn(orderSn);
        // 根据库存锁定单 ID 查询库存锁定状态为 1（已锁定）的库存锁定详情单列表
        List<WareOrderTaskDetailEntity> wareOrderTaskDetailList = wareOrderTaskDetailService.getTaskDetailListByTaskId(wareOrderTask.getId());
        for (WareOrderTaskDetailEntity wareOrderTaskDetail : wareOrderTaskDetailList) {
            unlockStock(wareOrderTaskDetail);
        }
    }

    /**
     * 执行锁定库存的逆操作来解锁库存
     *
     * @param wareOrderTaskDetail
     */
    private void unlockStock(WareOrderTaskDetailEntity wareOrderTaskDetail) {
        // UPDATE wms_ware_sku SET stock_locked = stock_locked - #{num} WHERE sku_id = #{skuId} AND ware_id = #{wareId}
        wareSkuDao.unlockStock(wareOrderTaskDetail.getSkuId(), wareOrderTaskDetail.getWareId(), wareOrderTaskDetail.getSkuNum());
        // 更新库存锁定详情单的状态为已解锁，1-已锁定 2-已解锁 3-扣减
        wareOrderTaskDetail.setLockStatus(2);
        wareOrderTaskDetailService.updateById(wareOrderTaskDetail);
    }

}